// Copyright (c) Microsoft Corporation
// All rights reserved

namespace Microsoft.Windows.Controls.Specialized.TextModel
{
    using System;
    using System.Diagnostics;

    /// <summary>
    /// The Text Change class describes an atomic text change operation on the Text Buffer.
    /// 
    /// All text changes are modeled as Replaces.  
    /// 
    /// Insertion: oldText == "" && newText != ""
    /// Deletion:  oldText != "" && newText == ""
    /// Replace:   oldText != "" && newText != ""
    /// </summary>
    public class TextChange
    {
        #region Private Members

        private int _position;
        private string _oldText, _newText;

        #endregion // Private Members

        /// <summary>
        /// Constructs a Text Change object
        /// </summary>
        /// <param name="position">
        /// The character position in the text buffer at which the text change happened.
        /// </param>
        /// <param name="oldText">
        /// The text that needs to be replaced.
        /// </param>
        /// <param name="newText">
        /// The text that replaces the old text
        /// </param>
        public TextChange(int position, string oldText, string newText)
        {
            _position = position;
            _oldText = oldText;
            _newText = newText;

            // Replace nulls
            if (_oldText == null)
                _oldText = "";
            if (_newText == null)
                _newText = "";
        }

        /// <summary>
        /// Given a text change that has been previously applied to the buffer, transform
        /// this change to account for position deltas caused by that change.
        /// This could produce an additional change (a deletion may need to be broken
        /// in two); it could modify the position of this change; it could render
        /// this change useless; or it could have no effect.
        /// </summary>
        /// <param name="change">
        /// The change, relative to which, we should transform the current text change
        /// </param>
        public TextChangeTransform Transform(TextChange change)
        {
            // In all cases, previous edits that occurred downstream are uninteresting
            if (change.Position > this.OldEnd)
                return new TextChangeTransform(false);

            // Previous edits that occured "completely" upstream of us, affect only the positions
            if (change.OldEnd <= this.Position)
            {
                _position += change.Delta;
                return new TextChangeTransform(false);
            }

            // We'll deal these in distinct cases.
            // Starting string: abcdefghi
            if (this.Position >= change.Position)
            {
                // Case 1: Replace "def" with "123" 
                //         Replace "efg" with "xyz"
                // Output: abc123xyzhi

                int overlap = Math.Min(change.OldEnd, this.OldEnd) - this.Position;
                _position = change.NewEnd;
                _oldText = _oldText.Substring(overlap);
                return new TextChangeTransform(false);
            }
            else
            {
                if (change.OldEnd >= this.OldEnd)
                {
                    // Case 2: Replace "de" with "123"
                    //         Replace "cde" with "xyz"
                    // Output: abxyz123fghi

                    int overlap = this.OldEnd - change.Position;
                    _oldText = _oldText.Substring(0, _oldText.Length - overlap);
                    return new TextChangeTransform(false);
                }
                else
                {
                    // We have a split deletion case!
                    // Case 3: Replace "e" with "123"
                    //         Replace "def" with "xyz"
                    // Output: abcxyz123ghi

                    int splitStart = change.Position - this.Position;

                    // Get the right half
                    string rightHalf = _oldText.Substring(splitStart + change.OldText.Length);
                    int rightHalfPosition = change.NewEnd;

                    _oldText = _oldText.Substring(0, splitStart);
                    return new TextChangeTransform(new TextChange(rightHalfPosition, rightHalf, ""));
                }
            }
        }

        #region Public Properties

        /// <summary>
        /// Gets the position of the Text Change
        /// </summary>
        public int Position
        {
            get
            {
                return _position;
            }
        }

        /// <summary>
        /// Gets the delta in the length of the text change
        /// </summary>
        public int Delta
        {
            get
            {
                return _newText.Length - _oldText.Length;
            }
        }

        /// <summary>
        /// Gets the End position before the text change
        /// </summary>
        public int OldEnd
        {
            get
            {
                return _position + _oldText.Length;
            }
        }

        /// <summary>
        /// Gets the End position after the text change
        /// </summary>
        public int NewEnd
        {
            get
            {
                return _position + _newText.Length;
            }
        }

        /// <summary>
        /// Gets the Text that got replaced
        /// </summary>
        public string OldText
        {
            get
            {
                return _oldText;
            }
        }

        /// <summary>
        /// Gets the Text that replaced the old text
        /// </summary>
        public string NewText
        {
            get
            {
                return _newText;
            }
        }

        #endregion // Public Properties
    }
}
